using System.Collections.Generic;
using System.Linq;
using Tycho.Parser.Peg;

namespace Tycho.Grammar {
    public class DynamicChoiceCompiler : IProductionCompiler {
        private readonly IProductionCompiler GrammarCompiler;
        private readonly IChoiceAssociativityRewriter ChoiceAssociativityRewriter;

        public DynamicChoiceCompiler (IProductionCompiler grammarCompiler, IChoiceAssociativityRewriter choiceAssociativityRewriter) {
            GrammarCompiler = grammarCompiler;
            ChoiceAssociativityRewriter = choiceAssociativityRewriter;
        }

        public GrammarTree Compile (ITerm term, ChoicePrecedence precedence, ProductionCompilerContext context) {
            var compositeTerm = (CompositeTerm) term;

            var choice = new ChoiceProduction ();
            var tree = new GrammarTree (choice);

            context.DeferredCompilations.AddCompilationStep (choice, () => DeferredCompilation (compositeTerm, context, choice, tree));

            return tree;
        }

        private void DeferredCompilation (CompositeTerm compositeTerm, ProductionCompilerContext context, ChoiceProduction choice, GrammarTree tree) {
            var choiceTerms = (ListTerm) compositeTerm.SubTerms["choices"];
            
            IEnumerable<NamedChoice> namedChoices = CompileNamedChoices (choiceTerms, choice, context);

            var productions = namedChoices.Select (c => c.GrammarTree.Production);
            context.Grammar.AddDynamicChoice (choice, productions);

            foreach (var production in productions) {
                context.DeferredCompilations.RunStepForObject (production);
            }

            var associativeProductions = ChoiceAssociativityRewriter.RewriteForAssciativity(productions, choice);
            choice.Productions = associativeProductions.ToArray ();

            foreach (var namedChoice in namedChoices) {
                string name = namedChoice.Name;

                if (name != null) {
                    tree.SubGrammars.Add (name, namedChoice.GrammarTree);
                }
            }
        }

        private IEnumerable<NamedChoice> CompileNamedChoices (ListTerm choiceTerms, IProduction choice, ProductionCompilerContext context) {
            var namedChoices = new List<NamedChoice> ();

            int order = 0;
            foreach (var c in choiceTerms.Terms) {
                namedChoices.Add (CompileChoice (c, new ChoicePrecedence (choice, order), context));
                order++;
            }

            return namedChoices;
        }

        private NamedChoice CompileChoice (ITerm term, ChoicePrecedence precedence, ProductionCompilerContext context) {
            if (term is CompositeTerm) {
                var compositeTerm = (CompositeTerm) term;

                if (compositeTerm.Name == "named-production") {
                    string name = ((IdentifierTerm) compositeTerm.SubTerms["name"]).Name;
                    GrammarTree production = GrammarCompiler.Compile (compositeTerm.SubTerms["production"], precedence, context);
                    return new NamedChoice (name, production);
                }
            }

            return new NamedChoice (null, GrammarCompiler.Compile (term, precedence, context));
        }

        private class NamedChoice {
            public readonly string Name;
            public readonly GrammarTree GrammarTree;

            public NamedChoice (string name, GrammarTree production) {
                Name = name;
                GrammarTree = production;
            }
        }
    }
}